Unified AI prompting
AIPrompt applies a synchronous model API to a Spark DataFrame, using Spark
partitions and bounded concurrent HTTP requests. It does not require the OpenAI
Python SDK. Existing OpenAIPrompt and OpenAIEmbedding stages remain available
with their own contracts. Migrating to AIPrompt is not a drop-in replacement:
for example, AIPrompt requires a configured model or applicable operation-specific
default rather than inheriting OpenAIPrompt's gpt-6-luna text deployment fallback.
The diagram summarizes routing, not universal provider support. Inherited setters
are validated against the selected API; a model can impose further restrictions.
Foundry Microsoft Decision-1 uses SystemOne. Foundry also exposes the separate
Decisions API for supported gpt-6-luna deployments; these are distinct protocols,
not interchangeable model names. Check the provider's model and region availability.
apiType | Input | Output |
|---|---|---|
chat_completions (default) | Prompt template, text column, or messages | First choice's text, or configured Spark postprocessing type |
responses | Prompt template, text column, or messages | Assistant text parts concatenated in response order, or configured postprocessing type |
systemone | Text/JSON state and named questions | Struct with returned model, lossless answer map and provider usage JSON |
decisions | Text or documented user messages and ordered questions | Struct with returned model, typed ordered answers and normalized usage |
embeddings | Text column, scalar text or prompt template | Spark ML vector |
Use AISystemOne and OpenAIDecisions directly when only that protocol is needed.
They share the facade's request validation, transport, diagnostics and persistence.
They have no default model. Model availability is provider-specific; selecting
an API never silently redirects to another API based on a model or hostname.
Inference is lazy: show, collect, repeated actions, recomputation and Spark
task retries can repeat paid requests. These stages do not persist input or
result datasets and do not require Delta. Provider store=true is unrelated to
persisting a Spark result. Plan how your application will materialize outcomes;
caching alone is not an exactly-once inference guarantee.
See the AIPrompt notebook for examples. Its runnable source is AIPrompt.ipynb.
PySpark and Scala
import os
from synapse.ml.services.ai import AIPrompt, AISystemOne
from synapse.ml.services.openai import OpenAIDecisions
prompt = (
AIPrompt()
.setApiType("responses")
.setUrl(os.environ["MODEL_ENDPOINT"])
.setDeploymentName(os.environ["MODEL_DEPLOYMENT"])
.setSubscriptionKey(os.environ["MODEL_API_KEY"])
.setPromptTemplate("Summarize this ticket: {ticket}")
.setOutputCol("answer")
.setErrorCol("error")
.setUsageCol("usage")
.setRawResponseCol("raw")
.setExtraBody({"text": {"verbosity": "low"}})
)
result = prompt.transform(tickets) # Lazy: no model calls yet.
import com.microsoft.azure.synapse.ml.services.ai.AIPrompt
val prompt = new AIPrompt()
.setApiType("responses")
.setUrl(sys.env("MODEL_ENDPOINT"))
.setDeploymentName(sys.env("MODEL_DEPLOYMENT"))
.setSubscriptionKey(sys.env("MODEL_API_KEY"))
.setPromptTemplate("Summarize this ticket: {ticket}")
.setOutputCol("answer").setErrorCol("error")
.setUsageCol("usage").setRawResponseCol("raw")
.setExtraBody("""{"text":{"verbosity":"low"}}""")
val result = prompt.transform(tickets)
Do not print credentials or save a credential-bearing stage into a shared model
directory. Standard SparkML stage persistence preserves configured Params,
including explicitly supplied authentication Params.
Use the corresponding stage's load/read API to restore a saved AIPrompt,
AISystemOne or OpenAIDecisions, or the standard Spark PipelineModel loader
for a saved pipeline. Copy and save/load preserve native JSON values, parameter
defaults and explicit bindings. Saving a stage stores its configuration, not
inference outcomes.
Pipeline.fit, cross-validation and repeated scoring can evaluate inference
more than once. Account for those model calls before placing a stage in a
training or tuning pipeline.
Choose exactly one input binding: promptTemplate, messagesCol, stateCol,
inputCol, or text/textCol. Bind native JSON strings with inputMode="json"
(setStateMode("json") is an alias); the default treats them as text.
For System One, stateCol and inputCol are supported but distinct persisted
Params, not aliases.
For Responses and Decisions, message structs may include type="message" or
a null/absent type; other non-null message types are rejected before HTTP.
Decisions JSON messages follow the same rule; a null type is omitted on the wire.
For inline Decisions images, null file_id and detail options are also omitted;
non-null file IDs remain unsupported. Decisions JSON input preserves provider
extension members; messagesCol uses the narrower Responses-compatible conversion.
Chat message structs do not accept this discriminator.
Spark's resolver selects the top-level messagesCol column; nested protocol keys
are case-sensitive JSON names such as role, content, type and name.
Duplicate field names anywhere within a bound message struct fail schema planning,
including for empty inputs, rather than silently discarding one of the values.
Explicit messages already contain their system instructions and attachments:
messagesCol cannot be combined with an explicitly set systemPrompt,
fileSizeLimitMB, dropPrompt, or non-empty columnTypes. For Decisions it
also cannot be combined with an explicitly set inputMode; message structs
are already structured input. These conflicts fail before HTTP.
Unlike legacy OpenAIPrompt, AIPrompt uses an explicitly set messagesCol
only as an input binding, not as the name of generated output messages.
With a prompt template and dropPrompt=False, retained messages use a UID-based,
collision-safe name reported by transformSchema; there is no separate naming
Param. Rename that column on the returned DataFrame if a stable consumer-facing
name is needed. Existing OpenAIPrompt behavior is unchanged.
Prompt templates must render a Spark string. Convert array or binary expressions
explicitly, for example with to_json or cast(... as string), before templating.
A template's outer projection must be row-local:
it cannot filter, aggregate or expand the input rows. Compute window expressions
(including window(...)), outer aggregates and generators as input columns first.
Templates may contain scalar SQL subqueries, including aggregates inside the
subquery. These queries can read other data again on each action.
Row-local templates also support command-result DataFrames such as SHOW TABLES
and SHOW COLUMNS.
Direct attachment placeholders follow Spark's column resolver and use the
attachment filename. Wildcards such as {to_json(struct(*))} still see only
the public input columns with their original values, not generated scratch fields.
Escaped braces and SQL expressions retain their usual template meaning.
Native and embedding input cells distinguish missing values from whitespace:
null or empty strings produce missing_input. System One and embeddings send
nonempty text unchanged, including whitespace-only text. Decisions requires
nonblank text, including text inside JSON messages; whitespace-only text produces
invalid_request without HTTP. Nonblank text is sent unchanged, not trimmed.
JSON mode requires a JSON value, so a whitespace-only JSON document also produces
invalid_request. Normalize or filter blank input before inference when your
application treats it as missing.